/* 
  SN76489 emulation
  by Maxim in 2001 and 2002
  converted from my original Delphi implementation

  I'm a C newbie so I'm sure there are loads of stupid things
  in here which I'll come back to some day and redo

  Includes:
  - Super-high quality tone channel "oversampling" by calculating fractional positions on transitions
  - Noise output pattern reverse engineered from actual SMS output
  - Volume levels taken from actual SMS output

  07/08/04  Charles MacDonald
  Modified for use with SMS Plus:
  - Added support for multiple PSG chips.
  - Added reset/config/update routines.
  - Added context management routines.
  - Removed SN76489_GetValues().
  - Removed some unused variables.
*/

#include "shared.h"

#define NoiseInitialState   0x8000  /* Initial state of shift register */
#define PSG_CUTOFF          0x6     /* Value below which PSG does not output */

static const int PSGVolumeValues[2][16] = {
/* These values are taken from a real SMS2's output */
  {892,892,892,760,623,497,404,323,257,198,159,123,96,75,60,0}, 
  /* these values are true volumes for 2dB drops at each step (multiply previous by 10^-0.1), normalised at 760 */
  {1516,1205,957,760,603,479,381,303,240,191,152,120,96,76,60,0}
};

static SN76489_Context SN76489[MAX_SN76489];

void SN76489_Init(int which, int PSGClockValue, int SamplingRate)
{
  SN76489_Context *p = &SN76489[which];
  p->dClock=(float)PSGClockValue/16/SamplingRate;
  SN76489_Reset(which);
}

void SN76489_Reset(int which)
{
  SN76489_Context *p = &SN76489[which];
  int i;

  p->PSGStereo = 0xFF;

  for(i = 0; i <= 3; i++)
  {
    /* Initialise PSG state */
    p->Registers[2*i] = 1;     /* tone freq=1 */
    p->Registers[2*i+1] = 0xf;   /* vol=off */
    p->NoiseFreq = 0x10;

    /* Set counters to 0 */
    p->ToneFreqVals[i] = 0;

    /* Set flip-flops to 1 */
    p->ToneFreqPos[i] = 1;

    /* Set intermediate positions to do-not-use value */
    p->IntermediatePos[i] = LONG_MIN;
  }

  p->LatchedRegister=0;

  /* Initialise noise generator */
  p->NoiseShiftRegister=NoiseInitialState;

  /* Zero clock */
  p->Clock=0;

}

void SN76489_Shutdown(void)
{
}

void SN76489_Config(int which, int mute, int boost, int volume, int feedback)
{
  SN76489_Context *p = &SN76489[which];

  p->Mute = mute;
  p->BoostNoise = boost;
  p->VolumeArray = volume;
  p->WhiteNoiseFeedback = feedback;
}

void SN76489_SetContext(int which, uint8 *data)
{
  memcpy(&SN76489[which], data, sizeof(SN76489_Context));
}

void SN76489_GetContext(int which, uint8 *data)
{
  memcpy(data, &SN76489[which], sizeof(SN76489_Context));
}

uint8 *SN76489_GetContextPtr(int which)
{
  return (uint8 *)&SN76489[which];
}

int SN76489_GetContextSize(void)
{
  return sizeof(SN76489_Context);
}

void SN76489_Write(int which, int data)
{
  SN76489_Context *p = &SN76489[which];

  if (data&0x80) {
    /* Latch/data byte  %1 cc t dddd */
    p->LatchedRegister=((data>>4)&0x07);
    p->Registers[p->LatchedRegister]=
      (p->Registers[p->LatchedRegister] & 0x3f0)  /* zero low 4 bits */
      | (data&0xf);               /* and replace with data */
  } else {
    /* Data byte    %0 - dddddd */
    if (!(p->LatchedRegister%2)&&(p->LatchedRegister<5))
      /* Tone register */
      p->Registers[p->LatchedRegister]=
        (p->Registers[p->LatchedRegister] & 0x00f)  /* zero high 6 bits */
        | ((data&0x3f)<<4);           /* and replace with data */
  else
      /* Other register */
      p->Registers[p->LatchedRegister]=data&0x0f;     /* Replace with data */
  }
  switch (p->LatchedRegister) {
  case 0:
  case 2:
  case 4: /* Tone channels */
    if (p->Registers[p->LatchedRegister]==0) p->Registers[p->LatchedRegister]=1;  /* Zero frequency changed to 1 to avoid div/0 */
  break;
  case 6: /* Noise */
    p->NoiseShiftRegister=NoiseInitialState;   /* reset shift register */
    p->NoiseFreq=0x10<<(p->Registers[6]&0x3);   /* set noise signal generator frequency */
  break;
  }
}

void SN76489_GGStereoWrite(int which, int data)
{
  SN76489_Context *p = &SN76489[which];
  p->PSGStereo=data;
}

void SN76489_Update(int which, INT16 **buffer, int length)
{
  SN76489_Context *p = &SN76489[which];
  int i, j;

  for(j = 0; j < length; j++)
  {
    for (i=0;i<=2;++i)
      if (p->IntermediatePos[i]!=LONG_MIN)
        p->Channels[i]=(p->Mute >> i & 0x1)*PSGVolumeValues[p->VolumeArray][p->Registers[2*i+1]]*p->IntermediatePos[i]/65536;
      else
        p->Channels[i]=(p->Mute >> i & 0x1)*PSGVolumeValues[p->VolumeArray][p->Registers[2*i+1]]*p->ToneFreqPos[i];
  
    p->Channels[3]=(short)((p->Mute >> 3 & 0x1)*PSGVolumeValues[p->VolumeArray][p->Registers[7]]*(p->NoiseShiftRegister & 0x1));
  
    if (p->BoostNoise) p->Channels[3]<<=1; /* Double noise volume to make some people happy */
  
    buffer[0][j] =0;
    buffer[1][j] =0;
    for (i=0;i<=3;++i) {
      buffer[0][j] +=(p->PSGStereo >> (i+4) & 0x1)*p->Channels[i]; /* left */
      buffer[1][j] +=(p->PSGStereo >>  i  & 0x1)*p->Channels[i]; /* right */
    }
  
    p->Clock+=p->dClock;
    p->NumClocksForSample=(int)p->Clock;  /* truncates */
    p->Clock-=p->NumClocksForSample;  /* remove integer part */
    /* Looks nicer in Delphi... */
    /*  Clock:=Clock+p->dClock; */
    /*  NumClocksForSample:=Trunc(Clock); */
    /*  Clock:=Frac(Clock); */
  
    /* Decrement tone channel counters */
    for (i=0;i<=2;++i)
      p->ToneFreqVals[i]-=p->NumClocksForSample;
   
    /* Noise channel: match to tone2 or decrement its counter */
    if (p->NoiseFreq==0x80) p->ToneFreqVals[3]=p->ToneFreqVals[2];
    else p->ToneFreqVals[3]-=p->NumClocksForSample;
  
    /* Tone channels: */
    for (i=0;i<=2;++i) {
      if (p->ToneFreqVals[i]<=0) {   /* If it gets below 0... */
        if (p->Registers[i*2]>PSG_CUTOFF) {
          /* Calculate how much of the sample is + and how much is - */
          /* Go to floating point and include the clock fraction for extreme accuracy :D */
          /* Store as long int, maybe it's faster? I'm not very good at this */
          p->IntermediatePos[i]=(long)((p->NumClocksForSample-p->Clock+2*p->ToneFreqVals[i])*p->ToneFreqPos[i]/(p->NumClocksForSample+p->Clock)*65536);
          p->ToneFreqPos[i]=-p->ToneFreqPos[i]; /* Flip the flip-flop */
        } else {
          p->ToneFreqPos[i]=1;   /* stuck value */
          p->IntermediatePos[i]=LONG_MIN;
        }
        p->ToneFreqVals[i]+=p->Registers[i*2]*(p->NumClocksForSample/p->Registers[i*2]+1);
      } else p->IntermediatePos[i]=LONG_MIN;
    }
  
    /* Noise channel */
    if (p->ToneFreqVals[3]<=0) {   /* If it gets below 0... */
      p->ToneFreqPos[3]=-p->ToneFreqPos[3]; /* Flip the flip-flop */
      if (p->NoiseFreq!=0x80)      /* If not matching tone2, decrement counter */
        p->ToneFreqVals[3]+=p->NoiseFreq*(p->NumClocksForSample/p->NoiseFreq+1);
      if (p->ToneFreqPos[3]==1) {  /* Only once per cycle... */
        int Feedback;
        if (p->Registers[6]&0x4) { /* White noise */
          /* Calculate parity of fed-back bits for feedback */
          switch (p->WhiteNoiseFeedback) {
            /* Do some optimised calculations for common (known) feedback values */
          case 0x0006:  /* SC-3000    %00000110 */
          case 0x0009:  /* SMS, GG, MD  %00001001 */
            /* If two bits fed back, I can do Feedback=(nsr & fb) && (nsr & fb ^ fb) */
            /* since that's (one or more bits set) && (not all bits set) */
  /* which one?     Feedback=((p->NoiseShiftRegister&p->WhiteNoiseFeedback) && (p->NoiseShiftRegister&p->WhiteNoiseFeedback^p->WhiteNoiseFeedback)); */
            Feedback=((p->NoiseShiftRegister&p->WhiteNoiseFeedback) && ((p->NoiseShiftRegister&p->WhiteNoiseFeedback)^p->WhiteNoiseFeedback));
            break;
          case 0x8005:  /* BBC Micro */
            /* fall through :P can't be bothered to think too much */
          default:    /* Default handler for all other feedback values */
            Feedback=p->NoiseShiftRegister&p->WhiteNoiseFeedback;
            Feedback^=Feedback>>8;
            Feedback^=Feedback>>4;
            Feedback^=Feedback>>2;
            Feedback^=Feedback>>1;
            Feedback&=1;
            break;
          }
        } else    /* Periodic noise */
          Feedback=p->NoiseShiftRegister&1;
  
        p->NoiseShiftRegister=(p->NoiseShiftRegister>>1) | (Feedback<<15);
  
  /* Original code: */
  /*      p->NoiseShiftRegister=(p->NoiseShiftRegister>>1) | ((p->Registers[6]&0x4?((p->NoiseShiftRegister&0x9) && (p->NoiseShiftRegister&0x9^0x9)):p->NoiseShiftRegister&1)<<15); */
      }
    }
  }
}

